package edu.yngsxy.wkstudy.core.codegen;

import edu.yngsxy.wkstudy.core.controller.WkBaseController;
import edu.yngsxy.wkstudy.core.dao.WkBaseMapper;
import edu.yngsxy.wkstudy.core.model.entity.WkBaseEntity;
import edu.yngsxy.wkstudy.core.service.WkBaseService;
import edu.yngsxy.wkstudy.core.service.impl.WkBaseServiceImpl;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.builder.Mapper;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.io.File;
import java.util.*;

public class CodeGenerator {
    public static void main(String[] args) {
        generate(CodeGeneratorConfig.create());
        //getTableNames(CodeGeneratorConfig.create()).forEach(System.out::println);
    }

    public static void generate(CodeGeneratorConfig config) {
        config.init();//初始化
        // 代码生成路径配置
        String projectPath = config.getProjectPath();
        String outputDir = projectPath + "/src/main/java";
        String mapperXmlOutputDir = projectPath + "/src/main/resources/"+config.getMapperXmlDir();
        //生成哪些表.如果config.getTables()为空，则使用getTableNames获取的表名并拼接
        Optional.ofNullable(config.getTables())// 当原表名为空时执行（获取表名并拼接）
                // 过滤空字符串（包括纯空白字符）
                .filter(str -> !str.strip().isEmpty())
                .orElseGet(() -> {
                    // 获取特定前缀的表名并转换为List
                    String prefix = config.getTablePrefix();
                    List<String> tbs;
                    // 前缀为空时直接使用原始列表，避免流操作开销
                    if (prefix == null || prefix.isBlank()) {
                        tbs = getTableNames(config);
                    } else {
                        // 提前计算小写前缀，避免重复转换
                        String lowerPrefix = prefix.toLowerCase();
                        tbs = getTableNames(config)
                                .stream()
                                .filter(tableName -> tableName.toLowerCase().startsWith(lowerPrefix))
                                .toList(); // Java 16+
                    }
                    // 获取表名列表并拼接成逗号分隔的字符串
                    String includeTables = String.join(",", tbs);
                    // 设置拼接后的表名到配置中
                    config.setTables(includeTables);
                    return includeTables;
                });
        Map<String, Object> customMap = config.getCustomMap();
        /*
        customMap.put("servicePackage",  config.getParentPackage() +
                (config.getModuleName() != null && !config.getModuleName().isEmpty() ? "." + config.getModuleName() : "") +
                ".service"); // service包名
        customMap.put("entityPackage",  config.getParentPackage() +
                (config.getModuleName() != null && !config.getModuleName().isEmpty() ? "." + config.getModuleName() : "") +
                ".entity"); // entity包名
                */
        //customMap.put("authorEmail", "dev@example.com"); // 作者邮箱
        //权限初始化SQL模板引擎
        PermissionsInitSqlFreemarkerTemplateEngine permissionsInitSqlFreemarkerTemplateEngine = new PermissionsInitSqlFreemarkerTemplateEngine(config, customMap);
        //数据库配置
        DataSourceConfig.Builder dsb = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(), config.getPassword());
        FastAutoGenerator.create(dsb)
                // 全局配置
                .globalConfig(builder -> {
                    builder.author(config.getAuthor()) // 设置作者
                            //.enableSpringdoc() // 开启 swagger/Springdoc 模式 不使用mybatis-plus提供的注解，手段通过自定义 模板实现接口文档自动生成
                            .outputDir(outputDir)// 指定输出目录
                            .disableOpenDir();//禁止自动打开输出目录 默认值: true
                })
                // 包配置
                .packageConfig(builder -> {
                    builder.parent(config.getParentPackage()) // 设置父包名
                            .moduleName(config.getModuleName()) // 设置父包模块名
                            .entity(config.getEntityPackage()) // 设置实体类包名
                            .mapper(config.getMapperPackage()) // 设置 Mapper 接口包名
                            .service(config.getServicePackage()) // 设置 Service 接口包名
                            .serviceImpl(config.getServiceImplPackage()) // 设置 Service 实现类包名
                            .controller(config.getControllerPackage()) // 设置 Controller 包名
                            //.xml("mappers"); // 设置 Mapper XML 文件包名,会与dao在同一包中
                            .pathInfo(Collections.singletonMap(OutputFile.xml, mapperXmlOutputDir)); // 设置mapperXml生成路径
                })
                //2. 配置InjectionConfig https://baomidou.com/reference/new-code-generator-configuration/
                //注入配置,允许开发者自定义代码生成器的行为，包括在输出文件之前执行的逻辑、自定义配置 Map 对象、自定义配置模板文件等。用于向模板注入自定义变量的配置类，通过它可以灵活地向 Controller、Service 等模板传递额外数据
                .injectionConfig(builder -> {
                    builder.beforeOutputFile((tableInfo, objectMap) -> {
                        //解析表对应的权限名称
                        permissionsInitSqlFreemarkerTemplateEngine.parsePermissions(tableInfo);
                        //从表注释中提取表名称和表描述
                        CommentMeta tableMeta = CodeGenerator.parseTableComment(tableInfo);
                        objectMap.put("tableTitle", tableMeta.title());
                        objectMap.put("tableDescription", tableMeta.description());
                        objectMap.put("wkContext", new CodeGenerator());// 注入自定义对象
                        //System.out.println("准备生成文件: " + tableInfo.getEntityName());
                        // 可以在这里添加自定义逻辑，如修改 objectMap 中的配置
                    }).customMap(customMap)
                    //.customMap(Collections.singletonMap("projectName", "MyBatis-Plus Generator"))
                    //.customFile(Collections.singletonMap("custom.txt", "/templates/permission_init.sql.ftl"))
                    ;
                })
                // 策略配置
                .strategyConfig(builder -> {
                    Mapper.Builder b = builder.addInclude(config.getTables()) // 设置需要生成的表名
                            .addTablePrefix(config.getTablePrefixDeleted()) // 设置过滤表前缀,生成的实体类名将会去除前缀
                            //.addTablePrefix("test_", "c_") // 设置过滤表前缀,生成的实体类名将会去除前缀
                            // 实体类策略配置
                            .entityBuilder()
                            .enableLombok() // 开启 Lombok
                            .enableTableFieldAnnotation() // 启用字段注解
                            .enableChainModel() // 开启链式模型
                            .javaTemplate("/templates/entity.java")
                            .superClass(WkBaseEntity.class)
                            //.enableActiveRecord()
                            // Controller策略配置
                            .controllerBuilder()
                            .template("/templates/controller.java")
                            .enableRestStyle() // 启用 REST 风格
                            // .enableHyphenStyle() // 开启驼峰转连字符
                            .formatFileName("%sController")
                            .superClass(WkBaseController.class)//设置父类
                            // Service策略配置
                            .serviceBuilder()
                            .superServiceClass(WkBaseService.class)
                            .superServiceImplClass(WkBaseServiceImpl.class)
                            .formatServiceFileName("%sService")
                            .formatServiceImplFileName("%sServiceImpl")
                            // Mapper策略配置
                            .mapperBuilder()
                            .superClass(WkBaseMapper.class)
                            .enableMapperAnnotation(); //设置父类
                    if (config.isEnableBaseResultMap()) b.enableBaseResultMap(); // 启用BaseResultMap生成
                    if (config.isEnableBaseColumnList()) b.enableBaseColumnList(); // 启用BaseColumnList
                    //.disable() // 禁用Mapper
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板，默认的是Velocity引擎模板
                .execute();
        // 当其他文件生成后，执行权限初始化SQL生成器生成权限初始化SQL
        permissionsInitSqlFreemarkerTemplateEngine.gen();
    }

    /**
     * 获取数据库所有表信息
     *
     * @param config
     * @return
     */
    public static List<TableInfo> getTables(CodeGeneratorConfig config) {
        return getConfigBuilder(config).getTableInfoList();
//        configBuilder.getTableInfoList().forEach(tableInfo -> {
//            // 原始表名（数据库中的表名）
//            String dbTableName = tableInfo.getName();
//            // 处理后的表名（去除前缀后的名称）
//            String processedTableName = tableInfo.getEntityName();
//            System.out.printf("数据库表名: %-20s 处理后实体名: %s%n", dbTableName, processedTableName);
//        });
    }

    public static ConfigBuilder getConfigBuilder(CodeGeneratorConfig config) {
        DataSourceConfig.Builder dsb = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(), config.getPassword());
        DataSourceConfig dataSourceConfig = dsb.build();
        PackageConfig packageConfig = new PackageConfig.Builder().build();
        StrategyConfig strategyConfig = new StrategyConfig.Builder().build();
        TemplateConfig templateConfig = new TemplateConfig.Builder().build();
        GlobalConfig globalConfig = new GlobalConfig.Builder().build();
        InjectionConfig injection = new InjectionConfig.Builder().build();
        ConfigBuilder configBuilder = new ConfigBuilder(packageConfig, dataSourceConfig, strategyConfig, templateConfig, globalConfig, injection);
        return configBuilder;
    }

    /**
     * 获取数据库表名列表
     *
     * @param config
     * @return
     */
    public static List<String> getTableNames(CodeGeneratorConfig config) {
        List<TableInfo> tableInfoList = getTables(config);
        return tableInfoList.stream().map(TableInfo::getName).filter(name -> !name.startsWith("test_"))  // 过滤掉test_开头的表名
                .toList();  // 转换为List（Java 16+）;
    }

    /**
     * 获取指定前缀的表名列表
     *
     * @param config
     * @param prefix
     * @return
     */
    public static List<String> getTableNamesByPrefix(CodeGeneratorConfig config, String prefix) {
        List<TableInfo> tableInfoList = getTables(config);
        return tableInfoList.stream().filter(tableInfo -> tableInfo.getName().startsWith(prefix)).map(TableInfo::getName).toList();
    }

    /**
     * 表元数据载体（Java 16+ Record特性，自动生成构造器和访问方法）
     * 用于封装从表注释中提取的表名称和表描述
     *
     * @param title       提取的表名称（优先从注释前缀获取，否则用原始表名）
     * @param description 提取的表描述（优先从注释后缀获取，否则用表名称）
     */
    public record CommentMeta(String title, String description) {
    }

    /**
     * 从表信息中提取表名称和表描述
     * 支持处理带中英文冒号的注释格式（如"表名:描述"或"表名：描述"）
     *
     * @param table MyBatis-Plus Generator解析的表信息对象
     * @return 封装了表名称和描述的TableMeta对象
     */
    public static CommentMeta parseTableComment(TableInfo table) {
        // 获取数据库表的原始名称（如"sys_user"）
        String originalTableName = table.getName();
        return parseComment(table.getComment(), originalTableName);
    }

    /**
     * 从表注释中提取表(字段)名称和表（字段）描述
     * 支持处理带中英文冒号的注释格式（如"表名:描述"或"表名：描述"）
     *
     * @param comment     表注释
     * @param defaultName 表（字段）名（当注释中无表（字段）名时使用）
     * @return 封装了表（字段）名称和描述的TableMeta对象
     */
    public static CommentMeta parseComment(String comment, String defaultName) {
        // 使用Optional处理可能为null的表注释，避免空指针异常
        System.out.println("原始注释：" + comment);
        return Optional.ofNullable(comment)
                // 1. 去除注释前后的空白字符（包括全角空格、制表符等Unicode空白）
                .map(String::strip)
                // 2. 过滤纯空白注释（如仅含空格、换行的注释）
                .filter(_comment -> !_comment.isBlank())
                // 3. 处理有效的非空注释
                .map(_comment -> {
                    System.out.println("原始注释：" + _comment);
                    // 先查找中文冒号“：”的位置（优先处理中文场景）
                    int colonIndex = _comment.indexOf("：");
                    // 若没有中文冒号，再查找英文冒号“:”
                    if (colonIndex == -1) {
                        colonIndex = _comment.indexOf(":");
                    }

                    // 3.1 无任何冒号：名称和描述都使用完整注释内容
                    if (colonIndex == -1) {
                        return new CommentMeta(_comment, _comment);
                    }

                    // 3.2 有冒号：拆分冒号前后的内容
                    // 提取冒号前的部分作为名称（并去除首尾空白）
                    String namePart = _comment.substring(0, colonIndex).strip();
                    // 提取冒号后的部分作为描述（并去除首尾空白）
                    String descPart = _comment.substring(colonIndex + 1).strip();

                    // 处理名称部分为空的情况（如注释以冒号开头），此时使用原始表名
                    String finalName = namePart.isBlank() ? defaultName : namePart;
                    // 处理描述部分为空的情况，此时使用名称作为描述
                    String finalDesc = descPart.isBlank() ? finalName : descPart;

                    return new CommentMeta(finalName, finalDesc);
                })
                // 4. 若注释为null或空白，名称和描述都使用原始表名
                .orElse(new CommentMeta(defaultName, defaultName));
    }

    /**
     * 权限初始化SQL生成器
     */
    static class PermissionsInitSqlFreemarkerTemplateEngine extends FreemarkerTemplateEngine {
        public record permissionMeta(String code, String name, String url) {
        }

        private final List<permissionMeta> permissions = new ArrayList<permissionMeta>();
        private CodeGeneratorConfig config;
        Map<String, Object> data;
        private String outputDir;
        // 权限操作列表
        public static Map<String, String> operationPermissions = Map.of(
                "add", "新增",
                "delete:*", "删除",
                "update", "修改",
                "get:*", "详情查询",
                "list", "列表查询",
                "*", "所有"
        );

        public PermissionsInitSqlFreemarkerTemplateEngine(CodeGeneratorConfig config, Map<String, Object> data) {
            permissions.clear();
            this.config = config;
            this.data = Optional.ofNullable(data).orElse(new HashMap<>());
            // 代码生成路径配置
            String projectPath = config.getProjectPath();
            this.outputDir = projectPath + "/src/main/java/" + config.getParentPackage().replace(".", File.separator) + File.separator + config.getModuleName();
            super.init(CodeGenerator.getConfigBuilder(config));
        }

        public void gen() {
            this.data.put("permissions", permissions);
            //传递当前时间（关键：确保模板能获取到时间变量）
            data.put("currentTime", new Date());  // 传递java.util.Date对象
            String genFile = String.format(outputDir + File.separator + "permissions_init.sql", "");
            this.outputFile(new File(genFile), data, this.templateFilePath("/templates/permission_init.sql"), true);
            this.data.remove("permissions");
        }

        /**
         * 根据表信息解析权限信息，并添加到权限列表中。权限格式：实体类名小写:操作，如：sysuser:add表示新增权限。sysuser:get表示sysuser表的查询详情权限。
         * sysuser:list表示sysuser表的查询列表权限。sysuser:update表示sysuser表的修改权限。sysuser:delete表示sysuser表的删除权限。
         *
         * @param table 表信息
         * @return
         */
        public PermissionsInitSqlFreemarkerTemplateEngine parsePermissions(TableInfo table) {
            CommentMeta tableMeta = CodeGenerator.parseTableComment(table);
            for (String op : operationPermissions.keySet()) {
                // 权限编码：表名:操作
                String permissionCode = table.getEntityName().toLowerCase() + ":" + op;
                // 权限名称：表描述+操作名称
                String permissionName = tableMeta.title() + operationPermissions.get(op) + "权限";
                permissions.add(new permissionMeta(permissionCode, permissionName, "/" + config.getControllerPathPrefix() + "/" + table.getEntityName() + "/" + op.replace(":", "/")));
            }
            return this;
        }
    }

}
